﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Reflection;
using System.Windows.Data;
using System.ComponentModel;

namespace RegexTester.Helpers
{
    public class Command : DependencyObject, ICommand, INotifyPropertyChanged
    {
        #region == Public Static Attributes ==

        #region TargetProperty
        public static readonly DependencyProperty TargetProperty =
            DependencyProperty.Register("Target", typeof(object), typeof(Command),
                                        new PropertyMetadata(OnViewModelPropertyChanged));
        #endregion

        #endregion

        #region == Private Attributes ==
        private EventHandler canExecuteChangedHandler;
        private object _target;
        private string _method;
        private bool _listeningForChanges;
        private PropertyInfo _canExecuteProperty;
        private MethodInfo _executeMethod;
        private bool _executeWithParameter; 
        #endregion

        #region == Public Methods ==

        #region Command
        public Command()
        {
            Binding binding = new Binding();
            BindingOperations.SetBinding(this, TargetProperty, binding);
        }
        #endregion

        #endregion

        #region == Public Properties ==

        #region Method
        public string Method
        {
            get
            {
                return _method;
            }
            set
            {
                _method = value;
                Update();
            }
        }
        #endregion

        #region Target
        public object Target
        {
            get
            {
                return GetValue(TargetProperty);
            }
            set
            {
                SetValue(TargetProperty, value);
            }
        }
        #endregion

        #endregion

        #region == Private Methods ==

        #region OnTargetChanged
        private void OnTargetChanged(object newTarget)
        {
            if (DesignerProperties.IsInDesignTool)
            {
                return;
            }

            if (_listeningForChanges)
            {
                ((INotifyPropertyChanged)_target).PropertyChanged -= OnTargetPropertyChanged;
                _listeningForChanges = false;
            }

            _target = newTarget;
            if (_target != null)
            {
                INotifyPropertyChanged inpc = _target as INotifyPropertyChanged;
                if (inpc != null)
                {
                    inpc.PropertyChanged += OnTargetPropertyChanged;
                    _listeningForChanges = true;
                }
            }

            Update();
        }
        #endregion

        #region OnTargetPropertyChanged
        private void OnTargetPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (String.CompareOrdinal(e.PropertyName, "Can" + Method) == 0)
            {
                if (canExecuteChangedHandler != null)
                {
                    canExecuteChangedHandler(this, EventArgs.Empty);
                }
            }
        }
        #endregion

        #region OnViewModelPropertyChanged
        private static void OnViewModelPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            ((Command)dependencyObject).OnTargetChanged(e.NewValue);
        } 
        #endregion

        #region Update
        private void Update()
        {
            if (_target != null && Method != null)
            {
                Type viewModelType = _target.GetType();
                _executeMethod = viewModelType.GetMethod(Method);
                _canExecuteProperty = viewModelType.GetProperty("Can" + Method);

                ParameterInfo[] parameters = _executeMethod.GetParameters();
                _executeWithParameter = ((parameters != null) && (parameters.Length == 1));
            }

            if (canExecuteChangedHandler != null)
            {
                canExecuteChangedHandler(this, EventArgs.Empty);
            }
        } 
        #endregion

        #endregion

        #region Implementation of ICommand

        #region ICommand.CanExecute
        bool ICommand.CanExecute(object parameter)
        {
            if (DesignerProperties.IsInDesignTool == false)
            {
                if (_target == null)
                {
                    return false;
                }

                //return this.CanExecute;

                if (_canExecuteProperty != null)
                {
                    return (bool)_canExecuteProperty.GetValue(_target, null);
                }
            }

            return true;
        } 
        #endregion

        #region ICommand.CanExecuteChanged
        event EventHandler ICommand.CanExecuteChanged
        {
            add
            {
                canExecuteChangedHandler = (EventHandler)Delegate.Combine(canExecuteChangedHandler, value);
            }
            remove
            {
                canExecuteChangedHandler = (EventHandler)Delegate.Remove(canExecuteChangedHandler, value);
            }
        } 
        #endregion

        #region ICommand.Execute
        void ICommand.Execute(object parameter)
        {
            if ((_target != null) && (_executeMethod != null))
            {
                if (_executeWithParameter)
                {
                    _executeMethod.Invoke(_target, new object[] { parameter });
                }
                else
                {
                    _executeMethod.Invoke(_target, null);
                }
            }
        } 
        #endregion

        #endregion

        #region Implementation of INotifyPropertyChanged
        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (String.CompareOrdinal(e.PropertyName, "CanExecute") == 0)
            {
                if (canExecuteChangedHandler != null)
                {
                    canExecuteChangedHandler(this, EventArgs.Empty);
                }
            }

            if (PropertyChanged != null)
                PropertyChanged(this, e);
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        } 
        #endregion
    }
}